﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace NetToolsV2
{
    public class IcmpEcho
    {
        private Thread pingThread;
        private Thread traceRouteThread;

        public delegate void PingResult(IcmpEchoReply reply);
        public delegate void PingError(string errorMessage);
        public delegate void PingFinished();

        public event PingResult OnPingResult;
        public event PingError OnPingError;
        public event PingFinished OnPingFinished;

        public string HostNameOrAddress { get; set; }
        public bool IsRunning { get; set; }
        public int NumberOfPings { get; set; }

        public IcmpEcho()
        {
            NumberOfPings = 4;
        }
        
        public void Ping(string hostNameOrAddress)
        {
            if (!IsRunning)
            {
                IsRunning = true;

                HostNameOrAddress = hostNameOrAddress;

                pingThread = new Thread(new ThreadStart(DoPing));
                pingThread.Start();
            }
        }

        public void TraceRoute(string hostNameOrAddress)
        {
            if (!IsRunning)
            {
                IsRunning = true;

                HostNameOrAddress = hostNameOrAddress;

                traceRouteThread = new Thread(new ThreadStart(DoTraceRoute));
                traceRouteThread.Start();
            }
        }

        private void DoPing()
        {
            IPHostEntry entry = Dns.GetHostEntry(HostNameOrAddress);

            if (entry == null || entry.AddressList.Length == 0)
            {
                FirePingError("Could not resolve " + HostNameOrAddress);
                IsRunning = false;
                return;
            }

            uint ipaddr = 0;
            for (int i = 0; i < entry.AddressList.Length; i++)
            {
                IPAddress address = entry.AddressList[i];
                if (address.AddressFamily == AddressFamily.InterNetwork)
                {
                    ipaddr = BitConverter.ToUInt32(address.GetAddressBytes(), 0);
                    break;
                }
            }

            if (ipaddr == 0)
            {
                FirePingError("Error getting address from address list");
                IsRunning = false;
                return;
            }

            byte[] RequestData = Encoding.ASCII.GetBytes(new string('\0', 64));

            //Allocate ICMP_ECHO_REPLY structure
            IcmpEchoReply reply = new IcmpEchoReply(255);
            reply.DataSize = 255;
            IntPtr pData = NativeMethods.LocalAlloc(NativeMethods.LMEM_ZEROINIT, reply.DataSize);
            reply.Data = pData;
            IntPtr h = NativeMethods.IcmpCreateFile();

            for (int i = 0; i < NumberOfPings; i++)
            {
                uint ret = NativeMethods.IcmpSendEcho(h, ipaddr, RequestData, (short)RequestData.Length, null, reply.Buffer, reply.Buffer.Length, 1000);
                int dwErr = 0;
                if (ret == 0)
                {
                    dwErr = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
                    if (dwErr != 11010)
                    {
                        FirePingError("Failed to ping. Error code: " + dwErr.ToString(CultureInfo.InvariantCulture));
                        IsRunning = false;
                        return;
                    }
                }
                if (dwErr != 11010)
                {
                    FirePingResult(reply);
                }
                else
                {
                    FirePingError("Request timed out");
                }

                System.Threading.Thread.Sleep(100);
            }
            NativeMethods.IcmpCloseHandle(h);
            NativeMethods.LocalFree(reply.Data);
            IsRunning = false;
            FirePingFinished();
        }

        private void DoTraceRoute()
        {
            IPHostEntry entry = Dns.GetHostEntry(HostNameOrAddress);

            if (entry == null || entry.AddressList.Length == 0)
            {
                FirePingError("Could not resolve " + HostNameOrAddress);
                IsRunning = false;
                return;
            }

            uint ipaddr = 0;
            for (int i = 0; i < entry.AddressList.Length; i++)
            {
                IPAddress address = entry.AddressList[i];
                if (address.AddressFamily == AddressFamily.InterNetwork)
                {
                    ipaddr = BitConverter.ToUInt32(address.GetAddressBytes(), 0);
                    break;
                }
            }

            if (ipaddr == 0)
            {
                FirePingError("Error getting address from address list");
                IsRunning = false;
                return;
            }

            byte[] RequestData = Encoding.ASCII.GetBytes(new string('\0', 64));

            //Allocate ICMP_ECHO_REPLY structure
            IcmpEchoReply reply = new IcmpEchoReply(255);
            reply.DataSize = 255;
            reply.Data = NativeMethods.LocalAlloc(NativeMethods.LMEM_ZEROINIT, reply.DataSize);

            IntPtr h = NativeMethods.IcmpCreateFile(); 
            
            for (int i = 1; i < 254; i++)
            {
                //Allocate IP_OPTION_INFORMATION structure
                IpOptionInformation options = new IpOptionInformation(16);
                options.Ttl = Convert.ToByte(i);

                uint ret = NativeMethods.IcmpSendEcho(h, ipaddr, RequestData, (short)RequestData.Length, options.Buffer, reply.Buffer, reply.Buffer.Length, 1000);
                int dwErr = 0;
                if (ret == 0)
                {
                    dwErr = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
                    if (dwErr != 11010)
                    {
                        FirePingError("Failed to ping. Error code: " + dwErr.ToString(CultureInfo.InvariantCulture));
                        IsRunning = false;
                        return;
                    }
                }
                if (dwErr != 11010)
                {
                    FirePingResult(reply);
                }
                else
                {
                    FirePingError("Request timed out");
                }

                if (reply.Address == ipaddr)
                {
                    break;
                }

                System.Threading.Thread.Sleep(100);

            }
            NativeMethods.LocalFree(reply.Data);
            NativeMethods.IcmpCloseHandle(h);
            IsRunning = false;
        }

        private void FirePingResult(IcmpEchoReply reply)
        {
            if (null != OnPingResult)
            {
                OnPingResult(reply);
            }
        }

        private void FirePingError(string errorMessage)
        {
            if (null != OnPingError)
            {
                OnPingError(errorMessage);
            }
        }

        private void FirePingFinished()
        {
            if (null != OnPingFinished)
            {
                OnPingFinished();
            }
        }
    }
}
